package net.glowstone.net.http;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollDatagramChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsServerAddresses;
import lombok.AllArgsConstructor;
import javax.net.ssl.SSLException;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.concurrent.TimeUnit;
public class HttpClient {
private static DnsAddressResolverGroup resolverGroup = new DnsAddressResolverGroup(Epoll.isAvailable() ? EpollDatagramChannel.class : NioDatagramChannel.class, DnsServerAddresses.defaultAddresses());
public static void connect(String url, EventLoop eventLoop, HttpCallback callback) {
URI uri = URI.create(url);
String scheme = uri.getScheme() == null ? "http" : uri.getScheme();
String host = uri.getHost() == null ? "127.0.0.1" : uri.getHost();
int port = uri.getPort();
SslContext sslCtx = null;
if ("https".equalsIgnoreCase(scheme)) {
if (port == -1) port = 443;
try {
sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} catch (SSLException e) {
callback.error(e);
return;
}
} else if ("http".equalsIgnoreCase(scheme)) {
if (port == -1) port = 80;
} else {
throw new IllegalArgumentException("Only http(s) is supported!");
}
new Bootstrap()
.group(eventLoop)
.resolver(resolverGroup)
.channel(Epoll.isAvailable() ? EpollSocketChannel.class : NioSocketChannel.class)
.handler(new HttpChannelInitializer(sslCtx, callback))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.connect(InetSocketAddress.createUnresolved(host, port))
.addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
String path = uri.getRawPath() + (uri.getRawQuery() == null ? "" : "?" + uri.getRawQuery());
HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
request.headers().set(HttpHeaderNames.HOST, host);
future.channel().writeAndFlush(request);
} else {
callback.error(future.cause());
}
});
}
@AllArgsConstructor
private static class HttpChannelInitializer extends ChannelInitializer<Channel> {
private SslContext sslCtx;
private HttpCallback callback;
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast("timeout", new ReadTimeoutHandler(6000, TimeUnit.MILLISECONDS));
if (sslCtx != null) {
channel.pipeline().addLast("ssl", sslCtx.newHandler(channel.alloc()));
}
channel.pipeline().addLast("codec", new HttpClientCodec());
channel.pipeline().addLast("handler", new HttpHandler(callback));
}
}
}